package db.freebase;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import db.freebase.annotations.FreeBaseIndex;
import db.freebase.entities.FreeBaseEntity;
import db.freebase.entities.FreeBaseEntityFactory;
import db.freebase.entities.FreeBaseFictionalCharacter;
import db.freebase.entities.FreeBaseFictionalDateTime;
import db.freebase.entities.FreeBaseFictionalEmpolymentTenure;
import db.freebase.entities.FreeBaseGenericMediator;

public class FreeBaseUtils {


    public static long midToLong(String mid) {
        if (!mid.startsWith("/m/")) {
            return -1;
        }
        mid = mid.substring(3);
        
        long result = 0;
        for (int i=mid.length()-1; i>=0; i--) {
            int factor = mid.length()-(i+1);
            int charValue = midCharToInt(mid.charAt(i));
            if (charValue == -1) {
                return -1;
            }
            result +=  Math.pow(32, factor) * charValue;
        }
        return result;
    }
    
    public static int midToInt(String mid) {
        long longMid = midToLong(mid);
        if (longMid > Integer.MAX_VALUE || !isValidMid((int)longMid)) {
            return -1;
        }
        return (int)longMid;
    }
    
    private static int midCharToInt(char c) {
        switch(c) {
        case '0': return 0;
        case '1': return 1;
        case '2': return 2;
        case '3': return 3;
        case '4': return 4;
        case '5': return 5;
        case '6': return 6;
        case '7': return 7;
        case '8': return 8;
        case '9': return 9;
        case 'b': return 10;
        case 'c': return 11;
        case 'd': return 12;
        case 'f': return 13;
        case 'g': return 14;
        case 'h': return 15;
        case 'j': return 16;
        case 'k': return 17;
        case 'l': return 18;
        case 'm': return 19;
        case 'n': return 20;
        case 'p': return 21;
        case 'q': return 22;
        case 'r': return 23;
        case 's': return 24;
        case 't': return 25;
        case 'v': return 26;
        case 'w': return 27;
        case 'x': return 28;
        case 'y': return 29;
        case 'z': return 30;
        case '_': return 31;
        default:  return -1;
        }
    }

    private static String fixStringValue(String original) {
        return original.replace(FreeBaseTSVLine.FreeBaseTSVLineValueReplacement1Original, 
                FreeBaseTSVLine.FreeBaseTSVLineValueReplacement1New).trim();
    }
    
    private static String getValue(FreeBaseTSVLine line, int index) {
        if (line.isNull(index)) {
            return null;
        } 
        return fixStringValue(line.getValueAt(index));
    }
    
    private static String[] getValues(FreeBaseTSVLine line, int index) {
        if (line.isNull(index)) {
            return null;
        } 
        String[] values = line.getValueAt(index).split(FreeBaseTSVLine.VALUE_SEPARATOR);
        List<String> fixedValues = new ArrayList<String>();
        for (String value : values) {
            if (!value.isEmpty()) {
                fixedValues.add(fixStringValue(value));
            }
        }
        return fixedValues.toArray(new String[0]);
    }
    
    private static float getFloat(FreeBaseTSVLine line, int index) {
        try {
            if (line.isValue(index)) {
                return Float.parseFloat(line.getValueAt(index));
            }
       } catch (NumberFormatException e) {
           // do nothing
       }
       return -1;
    }
    
    public static boolean isValue(String value) {
        return value != null && !value.isEmpty();
    }
    
    public static boolean isValue(String[] value) {
        return value != null && value.length > 0;
    }
    
    public static boolean isValue(float value) {
        return value != -1;
    }
    
    public static boolean isValidMid(int mid) {
        return mid != -1;
    }
    
    private static final Map<Field, FreeBaseIndex> annotationCache = new HashMap<Field, FreeBaseIndex>();
    
    static {
        cacheFreeBaseEntity(FreeBaseFictionalCharacter.class);
        cacheFreeBaseEntity(FreeBaseFictionalDateTime.class);
        cacheFreeBaseEntity(FreeBaseFictionalEmpolymentTenure.class);
        cacheFreeBaseEntity(FreeBaseGenericMediator.class);
    }
    
    private static<T extends FreeBaseEntity> void cacheFreeBaseEntity(Class<T> clazz) {
        for (Field field : clazz.getDeclaredFields()) {
            FreeBaseIndex index = field.getAnnotation(FreeBaseIndex.class);
            if (index != null) {
                annotationCache.put(field, index);
            }
        }
    }
    
    public static<T extends FreeBaseEntity> T parseLine(FreeBaseTSVLine line, Class<T> clazz) {
        
        T entity = FreeBaseEntityFactory.createInstance(clazz);
        
        for (Field field : clazz.getDeclaredFields()) {
            FreeBaseIndex index = annotationCache.get(field);
            if (index != null) {
                try {
                    field.setAccessible(true);
                    Type type = field.getType();
                    if (type.equals(String.class)) {
                        field.set(entity, getValue(line, index.index()));
                    } else if (type.equals(String[].class)) {
                        field.set(entity, getValues(line, index.index()));
                    } else if (type.equals(float.class)) {
                        field.set(entity, getFloat(line, index.index()));
                    } else {
                        throw new IllegalStateException("FreeBaseEntity holds illegal field");
                    }
                } catch (IllegalArgumentException e) {
                    throw e;
                } catch (IllegalAccessException e) {
                    throw new IllegalStateException("Illegal class state");
                } finally {
                    field.setAccessible(false);
                }
            }
                
        }
        
        return entity;
    }
    
}
